Skip to content

nvx-rs: add a guest mapping API with explicit export and target roots - #652

Merged
Pedro Henrique Penna (ppenna) merged 2 commits into
devfrom
esaurez/a1-guest-g4-mapping-roots-20261020
Oct 21, 2026
Merged

Pedro Henrique Penna (ppenna) merged 2 commits into
devfrom
esaurez/a1-guest-g4-mapping-roots-20261020

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

A guest init or agent can now apply decoded host mapping entries against roots that it names, through public APIs: an export root, where the host's shares appear, and a target root, the tree that the mappings go into. Until now the managed agent applied them against two implicit roots: its mount of the host's export, and absolute targets in its own root.

Part of #619 (step G4). Refs #535.

What changed

  • nvx::guest::mappings, new under the guest feature:
    • Mapping::new(source, target, access) takes a source relative to the export root, a target that is absolute as seen from the target root, and an Access, ReadOnly or ReadWrite. It refuses a path of more than MAX_PATH_BYTES bytes, Linux's PATH_MAX without its terminating NUL, with a NUL byte, or with an empty, ., or .. component, so that each path stays below its root, and InvalidMapping says which one. These are the MAPS decoder's rules, so every entry that the decoder accepts converts. A Mapping keeps its target relative to the target root, without the leading /, which target() returns.
    • Mapping::apply(export_root, target_root) takes both roots explicitly, as absolute paths. It keeps the managed agent's rules:
      • a directory source gets a directory target, and a regular-file source gets a regular-file target, created with O_CREAT | O_EXCL | O_NOFOLLOW;
      • any other source, and a file target that exists as anything but a regular file, is refused with EINVAL;
      • every mapping is bind-mounted, then remounted nosuid,nodev, and ro when it is read-only.
    • By path, it creates only the target's missing directories below the target root, and never a root. A failure is a MappingFailure with its MappingStage (source, target, bind or remount) and errno, and leaves what the earlier stages did.
    • It stays path-based, with today's checks. It adds no descriptor rooting, which nvx-rs: assemble and run the layered sandbox root in the guest library #621 adds later, and its public API has no control-protocol types.
  • The managed agent (guest/managed_agent/mappings.rs) converts each decoded MAPS entry into a Mapping, and applies it with hostfs_dir/root as the export root and / as the target root, through its system seam's mount. Its mount calls, their bytes and its errors are unchanged. It keeps the count announced on /proc/cmdline, the order of MAPS batches, and the latch after a failed mapping. The dispatcher is unchanged.
  • Docs: the crate, guest module and README documentation, and doc/project-structure.md, name the library.

tests/runtime-adapter doesn't call the API yet; a later A1-guest step does. dev has no public-API snapshot check, so there is no snapshot to update.

Tests

  • The managed agent's mapping and dispatch tests pass unchanged.
  • New tests of the library, with a recording mount:
    • directory and file mappings below an export root and a target root other than /, which create nothing outside the target root;
    • read-only and read-write remount flags, for directory and file mappings;
    • sources and targets that would leave their roots, relative roots, and a missing target root, which is never created;
    • sources that are symbolic links, sockets or missing, and file targets that exist as a directory or a symbolic link, which isn't followed;
    • existing targets, and a failed bind or remount, with its stage.
  • New tests of the managed agent:
    • a failure in the middle of a batch, at a bind or at a missing source: the earlier entry stays mounted, the rest of the batch isn't attempted, and the latch refuses every later batch;
    • every entry that the decoder accepts, including the longest paths and bytes that are not UTF-8, converts unchanged.

Sizes

Static x86_64-unknown-linux-musl guest profile, built with build-guest-binaries and the Alpine build-initramfs, natively in WSL Ubuntu with Rust 1.95.0, for dev at 5f189bdb and this head:

Artifact Base Head
nvx-managed-agent, stripped 483,808 bytes 483,808 bytes: .text grows from 365,598 to 367,086 bytes, and the other sections by 344 bytes in all, within the existing alignment padding
The other eight guest binaries Byte-identical
Alpine initramfs.cpio.gz 8,204,605 bytes 8,205,827 bytes (+1,222); uncompressed, 19,679,744 bytes for both

Validation

On this head, rebased onto dev at 5f189bdb, on Windows 11 with WHP and in WSL Ubuntu with KVM, except where a run names f70adb95, this head before its review fixes:

  • python scripts/nvx.py verify and python scripts/check_markdown_links.py.
  • In WSL Ubuntu, as root:
    • the steps of check-nvx-rs, in nvx-rs, with NVX_BUNDLE=skip: cargo fmt --all --check; cargo clippy --locked --all-targets --all-features -- -D warnings; cargo clippy --locked --lib --no-default-features -- -D warnings; cargo test --locked --all-features; cargo test --locked; the artifact-bundling test against a synthetic release; cargo doc --locked --no-deps --all-features with -D warnings; the 1.89 MSRV check; and the aarch64-apple-darwin check. As root, the caller-owned-share scenarios of tests/cli.rs fail, because they refuse a share that UID 0 owns, as they do on dev; with --no-fail-fast everything else passes.
    • python3 scripts/nvx.py check-guest-binaries, then build-guest-binaries --output <dir> and test-guest-binaries --binaries <dir>.
    • tests/runtime-adapter in its protocol-only, host-only, and musl guest-only configurations: rustfmt, Clippy, the build, the tests, and the MSRV check.
    • the steps of check-aci-edge-sandboxes: rustfmt, Clippy, the tests with all and with default features, test-agent-bundling, the documentation, the MSRV check, and the aarch64-apple-darwin check.
  • Real VMs, with the Alpine initramfs built from the head that a run names, whose nvx-managed-agent I checked by digest, the nvx-rs CLI built from it, and the kernel and OpenVMM of release v0.1.0-dev.d9859648556a, whose OpenVMM is the pinned d0bdbed8:
    • WHP: python scripts/nvx.py test-aci-edge-sandboxes --backend whp passed all 18 cases. Six of them map host directories and files into the guest; the seventh, which maps a directory on a second volume, returns early because this host's temporary directory has none. python scripts/nvx.py test-microvm --backend whp --scenario managed-lifecycle --scenario managed-exec-config --scenario sandbox-lifecycle passed. On f70adb95, the whole default suite on 1, 2, 4, and 8 processors passed too.
    • KVM, in WSL, on f70adb95: on this Alder Lake host, --cpu-profile auto selects a built-in profile that needs MSR bits that WSL's KVM doesn't expose, so OpenVMM ran through a local wrapper that adds --cpu-profile host. test-microvm --backend kvm --processors 1 passed guest-boot, lifecycle, managed-lifecycle, filesystem-shares, filesystem-policy, filesystem-comma-paths, filesystem-owner, denied-filesystem-paths, and filesystem-snapshot, then managed-exec-config and sandbox-lifecycle. test-aci-edge-sandboxes --backend kvm passed 13 of 18 cases. The 5 that write into read-write mappings fail with Permission denied, because the host side runs as root in WSL; with dev's agent, the initramfs of 5f189bdb, the same 5 fail in the same way.
  • Left to CI: MSHV, the other KVM and WHP scenarios, and the Ubuntu and Azure Linux guests with the new agent, from guest artifacts that CI builds from this head.

Copilot AI balanced review requested due to automatic review settings October 20, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified; the extraction preserves existing mapping behavior and adds focused regression coverage.

No open issues 🎉

What changed in this PR

Exposes host mapping operations as a reusable guest library, advancing #619 while preserving managed-agent behavior.

Changes:

  • Adds explicit export and target roots, path validation, and staged errors.
  • Migrates the managed agent to the library and adds regression tests.
  • Documents the public mapping API.
File Description
nvx-rs/​src/​lib.rs Introduces the mapping library in crate documentation.
nvx-rs/​src/​guest/​mod.rs Exports and documents mappings.
nvx-rs/​src/​guest/​mappings.rs Implements the public API and focused tests.
nvx-rs/​src/​guest/​managed_agent/​mappings.rs Uses the library while preserving sequencing and failure handling.
nvx-rs/​README.md Documents mapping APIs and agent integration.
doc/​project-structure.md Adds the library to the repository overview.

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) marked this pull request as ready for review October 20, 2026 19:37
Copilot AI balanced review requested due to automatic review settings October 21, 2026 00:58
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a1-guest-g4-mapping-roots-20261020 branch from ed2ba4f to 89d4400 Compare October 21, 2026 00:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

No blocking defects were identified, but the privileged mapping API’s path-based safety contract warrants human confirmation.

No open issues 🎉

Review effort: Balanced

The managed agent applied each decoded host mapping against two implicit
roots: its mount of the host's export, and absolute targets in its own root.
nvx::guest::mappings makes both explicit for any guest init or agent. A
Mapping holds a relative source below the export root, a target that is
absolute as seen from the target root, and its access, and refuses empty,
`.`, and `..` components, so that each path stays below its root.
Mapping::apply takes both roots. It keeps the agent's rules: a directory
source gets a directory target, a regular-file source gets a regular-file
target that it creates without following a symbolic link, any other source
is refused, and every bind is remounted nosuid and nodev, and read-only when
asked. A failure reports its stage and errno.

The managed agent passes hostfs_dir/root as the export root and / as the
target root, so its behavior is unchanged, and keeps the count that the
kernel command line announces, the order of MAPS batches, and the latch
after a failure.

Part of #619.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7f9f821d-e550-4890-b672-8aaad5b45b0c
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/a1-guest-g4-mapping-roots-20261020 branch from 89d4400 to f70adb9 Compare October 21, 2026 16:26
Copilot AI balanced review requested due to automatic review settings October 21, 2026 16:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Static review found no blocking issues in the focused extraction, integration, or regression coverage; tests were not executed during this review.

No open issues 🎉

Review effort: Balanced

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some questions.

Comment thread nvx-rs/src/guest/mappings.rs Outdated
Comment thread nvx-rs/src/guest/mappings.rs Outdated
Comment thread nvx-rs/src/guest/mappings.rs Outdated
Comment thread nvx-rs/src/guest/managed_agent/mappings.rs
A Mapping keeps its target relative to the target root: Mapping::new
strips the leading `/` once, after validation proves it, so applying a
mapping needs no expect. MAX_PATH_BYTES derives from Linux's PATH_MAX,
which guest::sys now names, and the modes of the directories and files
that a mapping creates are named constants. The adapter documents the
decoder's bounds on the paths that it converts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7f9f821d-e550-4890-b672-8aaad5b45b0c
Copilot AI balanced review requested due to automatic review settings October 21, 2026 21:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The extraction preserves existing behavior within its explicit path-based scope, includes targeted regression coverage, and has no identified blocking issues.

No open issues 🎉

Review effort: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants